# -*- coding: utf-8 -*-

"""
The Transitions.

"""

import logging
logger = logging.getLogger('pyknic.context.tarnsitions')

from . import _context



# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------

class Transition(_context.Context):
    """
    A special context class that represents a transition between two contexts.
    It represents a transition from the topmost context on the stack to the
    new_context. The topmost context will be exchanges with the new_context at
    the end.

    Transition B -> C::

                    +---+
                    | T |
        +---+       +---+       +---+
        | B |       | B |       | C |
        +---+       +---+       +---+
        | A |  -->  | A |  -->  | A |
        +---+       +---+       +---+
        |   |  push |   |  pop  |   |


    T: Transition
    A-C: Context
    """
    def __init__(self, 
                    new_context, 
                    transition_effect, 
                    think_old=False, 
                    think_new=False):
        """
        Constructor.

        :Parameters:
            new_context : Context
                The new context to transition to.
        """
        _context.Context.__init__(self)
        self.new_context = new_context
        self.old_context = None
        self.effect = transition_effect
        self.think_old = think_old
        self.think_new = think_new

    def think(self, delta_time):
        """
        Called once per frame. The default implementation just updates the
        effect and checks if the effect desires to end the transition. If
        the transition should react on keypresses, this method may be
        overriden, but keep in mind to call this base class method if
        the default behavior still should work.

        :Parameters:
            delta_time : float
                time passed since last frame
                
        :returns: True if it is done (finished, time is up or whatever) and does one pop, 
                    otherwise False.

        """
        if self.think_old:
            self.old_context.think(delta_time)
        if self.think_new:
            self.new_context.think(delta_time)
        if self.effect.think(delta_time):
            _context.pop()
            return True
        return False

    def suspend(self):
        """Called when another context is pushed on top of this one."""
        self.old_context.suspend()
        if __debug__:
            logger.debug("CONTEXT Trans: suspended " + \
                                    str(self.old_context))
        self.new_context.suspend()
        if __debug__:
            logger.debug("CONTEXT Trans: suspended " + \
                                    str(self.new_context))
        self.effect.suspend()

    def resume(self):
        """Called when another context is popped off the top of this one."""
        self.old_context.resume()
        if __debug__:
            logger.debug("CONTEXT Trans: resumed " + \
                                    str(self.old_context))
        self.new_context.resume()
        if __debug__:
            logger.debug("CONTEXT Trans: resumed" + \
                                    str(self.new_context))
        self.effect.resume()

    def enter(self):
        """Called when this context is pushed onto the stack."""
        self.new_context.enter()
        if __debug__:
            logger.debug("CONTEXT Trans: entered" + \
                                    str(self.new_context))
        self.old_context = _context.top()
        self.effect.enter()

    def exit(self):
        """Called when this context is removed from the stack."""
        self.effect.exit()

    def draw(self, screen):
        """
        Refresh the screen.

        :Parameters:
            screen : pygame.Surface
                the screen surface to draw on the final image.

        """
        self.effect.draw(screen, self.old_context, self.new_context)

    def process_stack_push(self, context_stack):
        """
        This is called to when this transition is pushed onto the stack.  
        Only the Transition knows what is has to do, that's why
        the stack changing code is here.
        """
        self.enter() # cont should be self
        if __debug__:
            logger.debug("CONTEXT Trans: entered" + \
                                    str(self))
        context_stack.append(self)
        if __debug__:
            logger.debug("CONTEXT Trans: pushed " + str(self))

    def process_stack_pop(self, context_stack, do_resume):
        """
        This is called to when this transition is removed from the stack.  
        Only the Transition knows what is has to do, that's why
        the stack changing code is here.
        """
        self.exit()
        if __debug__:
            logger.debug("CONTEXT Trans: exited " + \
                                        str(self))
        self.old_context.exit()
        if __debug__:
            logger.debug("CONTEXT Trans: exited " + \
                                    str(self.old_context))
        # exchange
        cont = context_stack.pop(-1)
        context_stack.append(self.new_context)
        if __debug__:
            logger.debug("CONTEXT Trans: replaced " + \
                                    str(cont) + " with " + \
                                    str(self.new_context))


# -----------------------------------------------------------------------------
# class TransitionFollow(Transition):
    # """
    # Transition B -> C -> D

                # +---+
                # | T |
    # +---+       +---+       +---+       +---+
    # | B |       | B |       | TF|       | D |
    # +---+       +---+       +---+       +---+
    # | A |  -->  | A |  -->  | A |  -->  | A |
    # +---+       +---+       +---+       +---+
    # |   |  push |   |  pop  |   | think |   |
                                    # pop

    # T: Transition
    # TF: TransitionFollow
    # A-C: Context
    # """

    # def think(self, delta_time):
        # super(TransitionFollow, self).think(delta_time)
        # if top() is self:
            # pop()

    # def exit(self):
        # """Called when this context is poped off the stack."""
        # exchange(self.new_context)

# -----------------------------------------------------------------------------
class PopTransition(Transition):
    """
    Transition B -> A::

                    +---+
                    | T |
        +---+       +---+
        | B |       | B |
        +---+       +---+       +---+
        | A |  -->  | A |  -->  | A |
        +---+       +---+       +---+
        |   |  push |   |  pop  |   |

    T: Transition
    A-B: Context
    """

    def __init__(self, transition_effect, think_old=False, think_new=False):
        """
        Constructor.

        :Note: it needs at least two context instance on the stack to work.

        """
        Transition.__init__(self, 
                            _context.top(1), 
                            transition_effect, 
                            think_old, 
                            think_new)

    def enter(self):
        """
        Called when this context is pushed onto the stack.
        
        .. todo:: make it handle None if there is only one context on the stack? -> use a dummy context

        """
        assert _context.length() >= 2
        self.old_context = _context.top()
        # TODO: make it handle None if there is only one context on the stack? 
        #       -> use a dummy context
        self.new_context = _context.top(1)
        self.effect.enter()

    def process_stack_push(self, context_stack):
        context_stack[-2].resume()
        if __debug__:
            logger.debug("CONTEXT PopTrans: resumed " + \
                                    str(context_stack[-2]))
        self.enter()
        if __debug__:
            logger.debug("CONTEXT PopTrans: entered " + \
                                            str(self))
        context_stack.append(self)
        if __debug__:
            logger.debug("CONTEXT PopTrans: pushed " + \
                                            str(self))

    def process_stack_pop(self, context_stack, do_resume):
        self.exit()
        if __debug__:
            logger.debug("CONTEXT PopTrans: exited " + \
                                            str(self))
        cont = context_stack.pop(-1)
        if __debug__:
            logger.debug("CONTEXT PopTrans: poped " + \
                                            str(cont))
        cont.exit()
        if __debug__:
            logger.debug("CONTEXT PopTrans: exited " + \
                                            str(cont))

# -----------------------------------------------------------------------------
class PushTransition(Transition):
    """

    Transition B -> C::

                    +---+       +---+
                    | T |       | C |
        +---+       +---+       +---+
        | B |       | B |       | B |
        +---+       +---+       +---+
        | A |  -->  | A |  -->  | A |
        +---+       +---+       +---+
        |   |       |   |       |   |

    T: Transition
    A-C: Context
    """

    def __init__(self, 
                    new_context, 
                    transition_effect, 
                    think_old=False, 
                    think_new=False):
        """
        Constructor.

        :Parameters:
            new_context : Context
                The new context to transition to.
        """
        Transition.__init__(self, new_context, transition_effect, \
                                                        think_old, think_new)
        self.old_context = None

    def enter(self):
        """Called when this context is pushed onto the stack."""
        self.new_context.enter()
        if __debug__:
            logger.debug("CONTEXT PushTrans: entered " + \
                                    str(self.new_context))
        self.old_context = _context.top()
        self.effect.enter()

    def process_stack_pop(self, context_stack, do_resume):
        self.exit()
        if __debug__:
            logger.debug("CONTEXT PushTrans: exited " + \
                                            str(self))
        if self.old_context:
            self.old_context.suspend()
            if __debug__:
                logger.debug("CONTEXT PushTrans: suspended " + \
                                    str(self.old_context))
        context_stack.append(self.new_context)
        if __debug__:
            logger.debug("CONTEXT PushTrans: pushed " + \
                                    str(self.new_context))

# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------

class TransitionEffect(object):
    """
    Abstract effect class. Inherit from it to implement a graphical 
    transition effect.
    """

    def enter(self):
        """
        Called once when the transition containing this effect is entered.
        """
        pass

    def exit(self):
        """
        Called once when the transition containing this effect is left.
        """
        pass

    def suspend(self):
        """
        Called once when the transition containing this effect is supended.
        """
        pass

    def resume(self):
        """
        Called once when the transition containing this effect is resumed.
        """
        pass

    def think(self, delta_time):
        """
        Any logic that needs to be updated for the effect.

        :Parameters:
            delta_time : float
                delta time passed since last frame

        :returns: True if it is done (finished, time is up or whatever), 
                    otherwise False.
        """
        raise NotImplementedError()

    def draw(self, screen, old_context, new_context):
        """
        Draws the effect on the screen surface.

        :Parameters:
            screen : pygame.Surface
                the screen surface, the finished drawing
            old_context : Context
                the old context, the one that where already here
            new_context : Context
                the new context, may eventually replace the old_context

        """
        raise NotImplementedError()

# -----------------------------------------------------------------------------

